use std::{fs, path};
use tantivy::collector::{FacetCollector, TopDocs};
use tantivy::directory::MmapDirectory;
use tantivy::query::{AllQuery, QueryParser};
use tantivy::schema::{Facet, Field, Schema};
use tantivy::{Document, Error, Index, IndexReader, IndexWriter, Opstamp};

pub struct Indexer<'a> {
    pub path: &'a path::Path,
    schema: Schema,
    index: Index,
    reader: IndexReader,
    writer: IndexWriter,
}

impl<'a> Indexer<'a> {
    pub fn new(target: &'a str, schema: Schema) -> Result<Indexer, Error> {
        let path = path::Path::new(target);

        if !path.is_dir() {
            fs::create_dir(target)?
        }

        let dir = MmapDirectory::open(path)?;
        let index = Index::open_or_create(dir, schema.clone())?;
        let reader = index.reader()?;
        let writer = index.writer(50_000_000)?;

        Ok(Indexer {
            path,
            schema,
            index,
            reader,
            writer,
        })
    }

    /// adds Document to index without checking for duplicates
    pub fn add_document(&self, doc: Document) -> Opstamp {
        self.writer.add_document(doc)
    }

    /// commits all changes to the index
    pub fn commit(&mut self) -> Result<Opstamp, Error> {
        self.writer.commit()
    }

    /// returns a Vec with all indexed Fields needed for QueryParser::for_index
    fn schema_fields(&self) -> Vec<Field> {
        let fields = self.schema.fields();

        (0..fields.len())
            .filter_map(|f| {
                if fields[f].is_indexed() {
                    Some(Field::from_field_id(f as u32))
                } else {
                    None
                }
            })
            .collect()
    }

    fn tags_collector(&self, tags: Vec<&'a str>) -> FacetCollector {
        let tags_field = self.schema.get_field("tags").unwrap();
        let mut collector = FacetCollector::for_field(tags_field);

        for tag in tags {
            collector.add_facet(tag);
        }
        collector
    }

    /// searches in all documents and by default in all indexed fields
    /// with the result set limited by limit
    pub fn search(&self, query_str: &'a str, limit: usize) -> Result<Vec<Document>, Error> {
        let searcher = self.reader.searcher();
        let parser = QueryParser::for_index(&self.index, self.schema_fields());
        let query = parser.parse_query(query_str)?;

        println!(
            "Searching {} documents for `{}`",
            searcher.num_docs(),
            query_str
        );
        let results = searcher.search(&query, &TopDocs::with_limit(limit))?;

        Ok(results
            .iter()
            .map(|(_score, address)| searcher.doc(*address).expect("search result should exist"))
            .collect())
    }

    pub fn search_facet(&self, query_str: &'a str) -> Result<(), Error> {
        let searcher = self.reader.searcher();
        let collector = self.tags_collector(vec![query_str]);
        let results = searcher.search(&AllQuery, &collector)?;

        let facet_counts: Vec<(&Facet, u64)> = results.get("/").collect();

        println!("tag search: {:?}", facet_counts);
        Ok(())
    }
}
